package org.wsiz.vro.web.filter;

import static java.lang.System.currentTimeMillis;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;

import org.wsiz.vro.security.UserContext;
import org.wsiz.vro.util.LogContext;

/**
 * Log current flow transition, state, etc. You must also configure this class as an Execution Listener in Spring Web Flow conf.
 */
public final class FlowTimerFilter extends FlowExecutionListenerAdapter implements Filter {

    private static final Logger log = Logger.getLogger(FlowTimerFilter.class);
    private static final ThreadLocal<StringBuilder> message = new ThreadLocal<StringBuilder>();

    @Override
    public void init(FilterConfig config) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // reset context.
        message.set(new StringBuilder(""));

        // set up log context for this thread so these information can be used by log4j
        String username = UserContext.getUsername();
        LogContext.setLogin(username != null ? username : "no username");
        LogContext.setSessionId(httpRequest.getSession().getId());

        // TODO: move temp hack
        // (partial ajax response use it. Required for IE8.)
        if (request.getCharacterEncoding() == null) {
            request.setCharacterEncoding("UTF-8");
        }

        // timer
        long start = currentTimeMillis();
        chain.doFilter(request, response);
        long duration = currentTimeMillis() - start;

        log.info(String.format("flow only duration: % 5d ms - %s%s", duration, httpRequest.getRequestURI(), message
                .get()));

        // reset context
        LogContext.resetLogContext();
    }

    @Override
    public void destroy() {
    }

    @Override
    public void viewRendered(RequestContext context, View view, StateDefinition viewState) {
        StringBuilder sb = message.get();
        if (sb == null) {
            log.info("viewRendered strange case: " + getHttpRequest(context).getRequestURI());
            return;
        }

        sb.append("\n\tActive flow: ") //
                .append(context.getActiveFlow() == null ? "N/A" : context.getActiveFlow().getId()) //
                .append(" (" + context.getFlowExecutionUrl() + ")") //
                .append("\n\tCurrent State: ") //
                .append((context.getCurrentState() == null ? "N/A" : context.getCurrentState().getId())) //
                .append("\n\tCurrent Transition: ") //
                .append(context.getCurrentTransition() == null ? "N/A" : context.getCurrentTransition().getId());
    }

    @Override
    public void eventSignaled(RequestContext context, Event event) {
        StringBuilder sb = message.get();
        if (sb == null) {
            log.info("eventSignaled strange case: " + getHttpRequest(context).getRequestURI());
            return;
        }

        sb.append("\n\teventSignaled: ").append(event.getId());
    }

    @Override
    public void transitionExecuting(RequestContext context, TransitionDefinition transition) {
        StringBuilder sb = message.get();
        if (sb == null) {
            log.info("transitionExecuting strange case: " + getHttpRequest(context));
            return;
        }

        sb.append("\n\ttransitionExecuting: ").append(
                "on=" + transition.getId() + " to=" + transition.getTargetStateId());
    }

    @Override
    public void exceptionThrown(RequestContext context, FlowExecutionException exception) {
        HttpServletRequest req = (HttpServletRequest) context.getExternalContext().getNativeRequest();
        req.getSession().setAttribute("lastException", exception);
        req.getSession().setAttribute("lastExceptionUniqueId", exception.hashCode());

        log.error("EXCEPTION unique id: " + exception.hashCode(), exception); // todo: better

        StringBuilder sb = message.get();
        if (sb == null) {
            log.info("exceptionThrown strange case: " + getHttpRequest(context).getRequestURI());
            return;
        }

        sb.append("\n\tException Thrown: ").append(exception);
        if (exception.getCause() != null) {
            sb.append("\n\tException Cause: ").append(exception.getCause());
        }
    }

    private HttpServletRequest getHttpRequest(RequestContext context) {
        return (HttpServletRequest) context.getExternalContext().getNativeRequest();
    }
}